package com.stone.serverless.sdk.handler;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.stone.serverless.sdk.utils.InvokeUtil;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * @Description: TODO
 * @Author: MECHREVO
 * @Date: 2022/1/14 13:56
 * @Version: 1.0.0
 */
public class ComponentInvocationHandler implements InvocationHandler, MethodInterceptor {
    private Object target;

    public static String[] EXCLUDE_METHOD_NAME = new String[]{"dataSource", "getJarModuleClassLoaders",
            "uninstall", "equals", "toString", "preHandle","getRouters","preHandle","invoke"};

    private static String[] EXCLUDE_CLASS_NAME = new String[]{"com.stone.serverless.core.JarLoadServiceImpl"
            ,"com.stone.serverless.event.service.EventBusService","com.stone.serverless.event.service.EventBus",
    "com.stone.serverless.beetl.service.MapperRegisterServiceImpl"};

    public ComponentInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        return this.proxy(proxy, method, args, target);
    }

    public Object proxy(Object proxy, Method method, Object[] args, Object target) throws Throwable {
        Class<?> aClass = target.getClass();
        Class<?>[] parameterTypes = method.getParameterTypes();

        String name = method.getName();
        Method[] declaredMethods = aClass.getDeclaredMethods();
        Method invokeMethod = null;
        for (Method m : declaredMethods) {
            if (m.getName().equals(name)) {
                //匹配参数个数 与类型
                Class<?>[] parameterTypes1 = m.getParameterTypes();
                if (parameterTypes1.length != parameterTypes.length) {
                    continue;
                }
                boolean mat = true;
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> arg = parameterTypes[i];
                    Class<?> aClass1 = parameterTypes1[i];
                    if (!arg.getName().equals(aClass1.getName())) {
                        mat = false;
                    }
                }
                if (!mat) {
                    continue;
                }
                invokeMethod = m;
                break;
            }
        }
        if (null == invokeMethod) {
            return null;
        }
        if (ArrayUtil.contains(EXCLUDE_CLASS_NAME, aClass.getName())) {
            return ReflectUtil.invoke(target, invokeMethod, args);
        }

        if (ArrayUtil.contains(EXCLUDE_METHOD_NAME, invokeMethod.getName())) {
            return ReflectUtil.invoke(target, invokeMethod, args);
        }

        Object obj = invokeMethod(invokeMethod, args, target);
        Class<?> returnType = method.getReturnType();
        if (returnType != void.class) {
            Type genericReturnType = method.getGenericReturnType();
            InvokeUtil.TypeInfo returnTypeInfo = InvokeUtil.getTypeInfo(returnType, genericReturnType, CharSequenceUtil.concat(true, returnType.getName(), StrPool.UNDERLINE, genericReturnType.getTypeName()));
            Object copy = InvokeUtil.copy(obj, returnTypeInfo);
            return copy;
        } else {
            return null;
        }
    }

    public static Object invokeMethod(Method targetMethod, Object[] arguments, Object tarBean) throws JsonProcessingException {

        Class<?>[] parameterTypes = targetMethod.getParameterTypes();
        Type[] genericParameterTypes = targetMethod.getGenericParameterTypes();
        Object[] params = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            InvokeUtil.TypeInfo typeInfo = InvokeUtil.getTypeInfo(parameterTypes[i], genericParameterTypes[i], CharSequenceUtil.concat(true, parameterTypes[i].getName(), StrPool.UNDERLINE, genericParameterTypes[i].getTypeName()));
            Object copy = InvokeUtil.copy(arguments[i], typeInfo);
            params[i] = copy;
        }
        Object invoke = ReflectUtil.invoke(tarBean, targetMethod, params);
        return invoke;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        return this.proxy(o, method, objects, target);
    }
}
